import os  # 需在程序顶部导入 os 模块
import matplotlib
matplotlib.use('Qt5Agg')  # 替换 TkAgg，与 PyQt5 兼容
import matplotlib.pyplot as plt
import sys
from ctypes import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import numpy as np

# 设置中文字体（确保中文显示正常）
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题



# 加载macOS动态库（注意路径！）
lib = CDLL('/Users/rose/python_/cpp_core/libmarket_clearing.dylib')


# 定义C风格结构体（与C++对应）
class PlantBid_C(Structure):
    _fields_ = [
        ("plantName", c_char_p),  # 电厂名称（C字符串）
        ("unitNo", c_char_p),  # 机组编号（C字符串）
        ("segmentCount", c_int),  # 报价段数（C整数）
        ("P", POINTER(c_double)),  # 出力段数组（C双精度指针）
        ("C", POINTER(c_double))  # 报价段数组（C双精度指针）
    ]


class UserBid_C(Structure):
    _fields_ = [
        ("userName", c_char_p),  # 用户名称（C字符串）
        ("userNo", c_char_p),  # 用户编号（C字符串）
        ("segmentCount", c_int),  # 报价段数（C整数）
        ("P", POINTER(c_double)),  # 负荷段数组（C双精度指针）
        ("C", POINTER(c_double))  # 报价段数组（C双精度指针）
    ]


class ClearingResult_C(Structure):
    _fields_ = [
        ("clearingPrice", c_double),
        ("totalClearedP", c_double),
        ("plantResultCount", c_int),
        ("plantNames", POINTER(c_char_p)),
        ("plantClearedP", POINTER(c_double)),
        ("plantIncome", POINTER(c_double)),
        ("userResultCount", c_int),
        ("userNames", POINTER(c_char_p)),
        ("userClearedP", POINTER(c_double)),
        ("userBill", POINTER(c_double))
    ]


# 初始化动态库
lib.initMarket()

# 测试动态库是否加载成功（如果不报错，说明路径对了）
print("C++动态库加载成功！")


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()  # 调用父类构造函数
        self.setWindowTitle("电力现货市场出清仿真平台")  # 设置窗口标题
        self.setGeometry(100, 100, 800, 600)  # 设置窗口位置和大小
        self.plants = []  # 初始化储存电厂数据的空列表，存储所有电厂的报价数据（每个元素是字典，含segmentCount、P、C等）
        self.users = []  # 初始化储存用户数据的空列表，存储所有用户的报价数据

        # 强制macOS菜单栏显示在窗口内
        self.menuBar().setNativeMenuBar(False)

        # 菜单
        menu_bar = self.menuBar()  # 获取或创建主窗口的菜单栏对象
        self.plant_menu = menu_bar.addMenu("发电侧")  # 在菜单栏上显示"发电侧"这个词，点击会弹出下拉菜单
        self.user_menu = menu_bar.addMenu("用户侧")
        self.trade_center_menu = menu_bar.addMenu("交易中心")

        # 添加导入菜单
        self.import_menu = menu_bar.addMenu("导入数据")

        # 导入发电侧数据
        self.import_plant_action = QAction("导入发电侧数据", self)
        self.import_plant_action.triggered.connect(self.importPlantData)  # 链接动作到导入函数
        self.import_menu.addAction(
            self.import_plant_action)  # 将"导入发电侧数据"这个动作（import_plant_action）添加到"导入数据"菜单（import_menu）中

        # 导入用户侧数据
        self.import_user_action = QAction("导入用户侧数据", self)
        self.import_user_action.triggered.connect(self.importUserData)
        self.import_menu.addAction(self.import_user_action)

        # 发电侧 - 提交报价
        self.submit_plant_action = QAction("提交阶梯报价", self)
        self.submit_plant_action.triggered.connect(self.openPlantBidWindow)
        self.plant_menu.addAction(self.submit_plant_action)

        # 用户侧 - 提交报价
        self.submit_user_action = QAction("提交阶梯报价", self)
        self.submit_user_action.triggered.connect(self.openUserBidWindow)
        self.user_menu.addAction(self.submit_user_action)

        # 交易中心 - 执行出清
        self.clear_market_action = QAction("执行市场出清", self)
        self.clear_market_action.triggered.connect(self.clearMarket)
        self.trade_center_menu.addAction(self.clear_market_action)

        # 中心显示
        self.main_widget = QWidget()
        self.main_layout = QVBoxLayout()
        self.main_widget.setLayout(self.main_layout)
        self.setCentralWidget(self.main_widget)

        self.status_label = QLabel("等待操作...")
        self.main_layout.addWidget(self.status_label)

    def importPlantData(self):
        """导入发电侧数据从CSV文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择发电侧数据文件", "", "CSV Files (*.csv)"
        )

        if not file_path:
            return

        try:
            # 读取CSV文件
            import pandas as pd
            df = pd.read_csv(file_path)

            # 假设CSV格式: 电厂名称,机组编号,段数,出力1,报价1,出力2,报价2,...
            plants_data = {}

            for _, row in df.iterrows():
                plant_name = row[0]
                unit_no = row[1]
                segment_count = int(row[2])

                P = []
                C = []

                for i in range(segment_count):
                    P.append(float(row[3 + i * 2]))
                    C.append(float(row[4 + i * 2]))

                if plant_name not in plants_data:
                    plants_data[plant_name] = {
                        "plantName": plant_name,
                        "units": []
                    }

                plants_data[plant_name]["units"].append({
                    "unitNo": unit_no,
                    "segmentCount": segment_count,
                    "P": P,
                    "C": C
                })

            # 添加到现有数据
            for plant_data in plants_data.values():
                self.plants.append(plant_data)

                # 提交到C++库
                plant_name_bytes = plant_data["plantName"].encode('utf-8')
                plant_name_c = cast(create_string_buffer(plant_name_bytes), c_char_p)

                for unit in plant_data["units"]:
                    segment_count = unit["segmentCount"]
                    P_arr = (c_double * segment_count)(*unit["P"])
                    C_arr = (c_double * segment_count)(*unit["C"])

                    unit_no_bytes = unit["unitNo"].encode('utf-8')
                    unit_no_c = cast(create_string_buffer(unit_no_bytes), c_char_p)

                    bid = PlantBid_C()
                    bid.plantName = plant_name_c
                    bid.unitNo = unit_no_c
                    bid.segmentCount = segment_count
                    bid.P = P_arr
                    bid.C = C_arr

                    lib.submitPlantBid(byref(bid))

            QMessageBox.information(self, "成功", f"成功导入 {len(plants_data)} 个电厂数据")

        except Exception as e:
            QMessageBox.warning(self, "错误", f"导入数据失败: {str(e)}")

    def importUserData(self):
        """导入用户侧数据从CSV文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择用户侧数据文件", "", "CSV Files (*.csv)"
        )

        if not file_path:
            return

        try:
            # 读取CSV文件
            import pandas as pd
            df = pd.read_csv(file_path)

            # 假设CSV格式: 用户名称,用户编号,段数,负荷1,报价1,负荷2,报价2,...
            for _, row in df.iterrows():
                user_name = row[0]
                user_no = row[1]
                segment_count = int(row[2])

                P = []
                C = []

                for i in range(segment_count):
                    P.append(float(row[3 + i * 2]))
                    C.append(float(row[4 + i * 2]))

                user_data = {
                    "userName": user_name,
                    "userNo": user_no,
                    "segmentCount": segment_count,
                    "P": P,
                    "C": C
                }

                self.users.append(user_data)

                # 提交到C++库
                user_name_bytes = user_name.encode('utf-8')
                user_no_bytes = user_no.encode('utf-8')
                user_name_c = cast(create_string_buffer(user_name_bytes), c_char_p)
                user_no_c = cast(create_string_buffer(user_no_bytes), c_char_p)

                P_arr = (c_double * segment_count)(*P)
                C_arr = (c_double * segment_count)(*C)

                bid = UserBid_C()
                bid.userName = user_name_c
                bid.userNo = user_no_c
                bid.segmentCount = segment_count
                bid.P = P_arr
                bid.C = C_arr

                lib.submitUserBid(byref(bid))

            QMessageBox.information(self, "成功", f"成功导入 {len(df)} 个用户数据")

        except Exception as e:
            QMessageBox.warning(self, "错误", f"导入数据失败: {str(e)}")

    # 打开发电侧报价窗口
    def openPlantBidWindow(self):
        # 用 exec_() 模态打开窗口（等待窗口关闭后再执行后续逻辑）
        self.plant_bid_window = PlantBidWindow(self)
        if self.plant_bid_window.exec_() == QDialog.Accepted:
            # 调用窗口的 get_submitted_data() 获取提交的字典数据
            plant_data = self.plant_bid_window.get_submitted_data()
            if plant_data:  # 确保数据不为空
                self.plants.append(plant_data)  # 存入 MainWindow 的 plants 列表
                # 更新状态栏提示（可选，方便确认数据已存入）
                self.status_label.setText(f"已提交 {len(self.plants)} 个电厂报价")

    # 打开用户侧报价窗口
    def openUserBidWindow(self):
        # 同理，模态打开用户侧窗口
        self.user_bid_window = UserBidWindow(self)
        if self.user_bid_window.exec_() == QDialog.Accepted:
            user_data = self.user_bid_window.get_submitted_data()
            if user_data:
                self.users.append(user_data)  # 存入 MainWindow 的 users 列表
                self.status_label.setText(f"已提交 {len(self.users)} 个用户报价")

    # 执行出清（核心修改：完善指针校验 + 确保freeClearingResult必调用）
    # 修改 clearMarket 方法
    def clearMarket(self):
        # 1. 先计算正确的出清点（Python侧）
        supply_curve = self.calculate_supply_curve()
        demand_curve = self.calculate_demand_curve()
        python_clearing_p, python_clearing_price = self.find_clearing_point(supply_curve, demand_curve)

        print(f"Python计算出清点: {python_clearing_p}MW, {python_clearing_price}元")

        # 2. 调用C++动态库
        lib.clearMarket.argtypes = []
        lib.clearMarket.restype = POINTER(ClearingResult_C)
        result_ptr = lib.clearMarket()

        if not result_ptr:
            QMessageBox.warning(self, "错误", "动态库返回空指针，出清失败！")
            return

        # 3. 获取C++结果
        result = result_ptr.contents

        print(f"C++计算出清点: {result.totalClearedP}MW, {result.clearingPrice}元")

        # 4. 用Python计算的结果覆盖C++的结果（确保表格和图像一致）
        result.totalClearedP = python_clearing_p
        result.clearingPrice = python_clearing_price

        # 5. 显示结果表格
        self.showClearingResult(result)

        # 6. 绘制曲线（使用Python计算的结果）
        self.plot_clearing_curve(python_clearing_p, python_clearing_price, result)

        # 7. 释放内存
        lib.freeClearingResult(result_ptr)

        # -------------------------- 核心：确保调用绘图函数（必须加！）--------------------------
        '''
                ''''''
        print("=== 准备调用绘图函数 ===")
        print(f"出清结果中的总电量：{result.totalClearedP}，电价：{result.clearingPrice}")
        print(f"MainWindow中存储的电厂数据数：{len(self.plants)}")
        print(f"MainWindow中存储的用户数据数：{len(self.users)}")
        # 强制调用绘图函数（不管其他，先触发）
        try:
            self.plot_clearing_curve(
                clearing_p=result.totalClearedP,
                clearing_price=result.clearingPrice
            )
            print("=== 绘图函数调用完成 ===")
        except Exception as e:
            # 捕获所有错误，打印出来（避免调用失败却没日志）
            print(f"=== 绘图函数调用出错：{e} ===")
        # -----------------------------------------------------------------------------------


        '''

        print("=== 验证数据 ===")
        print(f"发电侧数据列表长度：{len(self.plants)}，数据：{self.plants}")
        print(f"用户侧数据列表长度：{len(self.users)}，数据：{self.users}")
        print(f"出清电量：{result.totalClearedP}，出清电价：{result.clearingPrice}")
        print("=== 验证结束 ===")

    # 展示结果（修复缩进错误，确保是MainWindow类的方法）
    def showClearingResult(self, result):
        result_window = QDialog()  # 改用QDialog，支持exec_()模态显示
        result_window.setWindowTitle("出清结果")
        layout = QVBoxLayout()

        # 出清电价、总电量
        price_label = QLabel(f"出清电价：{result.clearingPrice:.2f} 元/MWh")
        total_p_label = QLabel(f"总出清电量：{result.totalClearedP:.2f} MW")
        layout.addWidget(price_label)
        layout.addWidget(total_p_label)

        # 发电侧结果表格
        if result.plantResultCount > 0:  # 增加有效性判断
            plant_table = QTableWidget(result.plantResultCount, 3)
            plant_table.setHorizontalHeaderLabels(["电厂名称", "出清电量（MW）", "收入（元）"])
            for i in range(result.plantResultCount):
                # 安全解析：避免空指针导致崩溃
                plant_name = result.plantNames[i].decode() if result.plantNames[i] else "未知电厂"
                cleared_p = f"{result.plantClearedP[i]:.2f}" if result.plantClearedP else "0.00"
                income = f"{result.plantIncome[i]:.2f}" if result.plantIncome else "0.00"

                plant_table.setItem(i, 0, QTableWidgetItem(plant_name))
                plant_table.setItem(i, 1, QTableWidgetItem(cleared_p))
                plant_table.setItem(i, 2, QTableWidgetItem(income))
            layout.addWidget(plant_table)
        else:
            layout.addWidget(QLabel("无发电侧出清结果"))

        # 用户侧结果表格
        if result.userResultCount > 0:  # 增加有效性判断
            user_table = QTableWidget(result.userResultCount, 3)
            user_table.setHorizontalHeaderLabels(["用户名称", "出清负荷（MW）", "账单（元）"])
            for i in range(result.userResultCount):
                # 安全解析：避免空指针导致崩溃
                user_name = result.userNames[i].decode() if result.userNames[i] else "未知用户"
                cleared_p = f"{result.userClearedP[i]:.2f}" if result.userClearedP else "0.00"
                bill = f"{result.userBill[i]:.2f}" if result.userBill else "0.00"

                user_table.setItem(i, 0, QTableWidgetItem(user_name))
                user_table.setItem(i, 1, QTableWidgetItem(cleared_p))
                user_table.setItem(i, 2, QTableWidgetItem(bill))
            layout.addWidget(user_table)
        else:
            layout.addWidget(QLabel("无用户侧出清结果"))

        result_window.setLayout(layout)
        result_window.exec_()

    # 假设你从 C++ 动态库获取到以下数据（需根据你的实际变量名调整）
    # 1. 从 C++ 侧读取供需曲线数据（这里需要你在 C++ 侧把 curve 数据返回给 Python，或在 Python 侧重新计算，推荐后者）
    # 重新计算供应曲线（按 C++ 逻辑，发电侧分段按报价升序累加）
    # 重新计算供应曲线（补充self，且使用self.plants获取数据）

    def calculate_supply_curve(self):
        """全局排序后生成供应曲线：将所有电厂的报价段按价格升序排列后累加"""
        all_segments = []

        # 收集所有电厂的报价段
        for plant in self.plants:
            if "units" not in plant:
                continue

            for unit in plant["units"]:
                if not unit.get("P") or not unit.get("C"):
                    continue

                seg_count = unit["segmentCount"]
                for i in range(seg_count):
                    p = unit["P"][i]
                    c = unit["C"][i]
                    if p <= 0 or c <= 0:
                        continue
                    all_segments.append((p, c))  # 保存 (出力段, 报价)

        # 按报价升序排序
        all_segments.sort(key=lambda x: x[1])

        # 累加生成供应曲线
        segments = []
        cum_p = 0.0
        for p, c in all_segments:
            cum_p += p
            segments.append((cum_p, c))

        return segments

    # 重新计算需求曲线（用户侧分段按报价降序累加）
    def calculate_demand_curve(self):
        """全局排序后生成需求曲线：将所有用户的报价段按价格降序排列后累加"""
        all_segments = []

        # 收集所有用户的报价段
        for user in self.users:
            if not user.get("P") or not user.get("C"):
                continue

            seg_count = user["segmentCount"]
            for i in range(seg_count):
                p = user["P"][i]
                c = user["C"][i]
                if p <= 0 or c <= 0:
                    continue
                all_segments.append((p, c))  # 保存 (负荷段, 报价)

        # 按报价降序排序
        all_segments.sort(key=lambda x: x[1], reverse=True)

        # 累加生成需求曲线
        segments = []
        cum_p = 0.0
        for p, c in all_segments:
            cum_p += p
            segments.append((cum_p, c))

        return segments

    def find_clearing_point(self, supply_curve, demand_curve):
        """找到供需曲线的实际交叉点 - 简单直接版本"""
        if not supply_curve or not demand_curve:
            return (0.0, 0.0)

        # 找到第一个供应价格超过需求价格的区间
        clearing_p = 0.0
        clearing_price = 0.0

        # 遍历所有可能的电量点
        for i in range(len(supply_curve)):
            supply_p, supply_price = supply_curve[i]

            # 找到对应的需求价格
            demand_price = 0.0
            for j in range(len(demand_curve)):
                demand_p, temp_demand_price = demand_curve[j]
                if demand_p >= supply_p:
                    demand_price = temp_demand_price
                    break

            # 检查是否有交叉
            if supply_price <= demand_price:
                clearing_p = supply_p
                clearing_price = (supply_price + demand_price) / 2
            else:
                # 供应价格超过需求价格，说明已经过了交点
                break

        return (clearing_p, clearing_price)

    def plot_clearing_curve(self, clearing_p, clearing_price, result=None):
        # 1. 生成严格分段的供需曲线
        supply_curve = self.calculate_supply_curve()
        demand_curve = self.calculate_demand_curve()

        # 2. 解析曲线数据为绘图可用的 x/y
        def parse_curve(curve):
            if not curve:
                return [0.0, 1.0], [0.0, 0.0]

            x = [0.0]
            y = [curve[0][1]] if curve else [0.0]

            # 添加阶梯点
            for cum_p, price in curve:
                # 添加水平线段的起点
                if len(x) > 0:
                    x.append(x[-1])
                    y.append(price)
                # 添加阶梯点
                x.append(cum_p)
                y.append(price)

            return x, y

        supply_x, supply_y = parse_curve(supply_curve)
        demand_x, demand_y = parse_curve(demand_curve)

        # 3. 重新计算正确的出清点
        calc_clearing_p, calc_clearing_price = self.find_clearing_point(supply_curve, demand_curve)

        print(f"供应曲线: {supply_curve}")
        print(f"需求曲线: {demand_curve}")
        print(f"计算出清点: {calc_clearing_p}MW, {calc_clearing_price}元")

        # 4. 绘制阶梯曲线
        plt.figure(figsize=(12, 8))
        plt.step(supply_x, supply_y, where='post', color='red', label='供应曲线', linewidth=2)
        plt.step(demand_x, demand_y, where='post', color='blue', label='需求曲线', linewidth=2)

        # 5. 绘制出清点
        if calc_clearing_p > 0 and calc_clearing_price > 0:
            plt.scatter(
                calc_clearing_p, calc_clearing_price,
                color='green', s=150, zorder=5, marker='o',
                label=f'出清点({calc_clearing_p:.2f}MW, {calc_clearing_price:.2f}元)'
            )

            # 添加出清点的参考线
            plt.axvline(x=calc_clearing_p, color='gray', linestyle='--', alpha=0.7)
            plt.axhline(y=calc_clearing_price, color='gray', linestyle='--', alpha=0.7)

        # 6. 动态计算坐标轴范围
        max_x = max(max(supply_x) if supply_x else 0,
                    max(demand_x) if demand_x else 0,
                    calc_clearing_p * 1.2)
        max_y = max(max(supply_y) if supply_y else 0,
                    max(demand_y) if demand_y else 0,
                    calc_clearing_price * 1.2)

        plt.xlim(0, max_x * 1.1)
        plt.ylim(0, max_y * 1.1)

        # 7. 图表样式设置
        plt.xlabel('电量 (MW)', fontsize=12)
        plt.ylabel('电价 (元/MWh)', fontsize=12)
        plt.title('电力现货市场供需阶梯曲线与出清点', fontsize=14, pad=20)
        plt.legend(fontsize=10, loc='upper right')
        plt.grid(True, alpha=0.3)

        # 8. 保存图片
        img_path = '/Users/rose/python_/py_gui/clearing_curve.png'
        plt.savefig(img_path, dpi=300, bbox_inches='tight')
        plt.close(1)

        # 9. 显示图片
        os.system(f'open "{img_path}"')

        # 10. 图片关闭后显示综合结果窗口
        self.showCombinedResult(result, img_path) if result else None

    def showCombinedResult(self, result, img_path):
        """显示包含表格和图片的综合结果窗口"""
        result_window = QDialog()
        result_window.setWindowTitle("出清综合结果")
        result_window.setGeometry(100, 100, 1200, 800)

        main_layout = QHBoxLayout()

        # 左侧：结果表格
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)

        # 出清电价、总电量
        price_label = QLabel(f"出清电价：{result.clearingPrice:.2f} 元/MWh")
        total_p_label = QLabel(f"总出清电量：{result.totalClearedP:.2f} MW")
        left_layout.addWidget(price_label)
        left_layout.addWidget(total_p_label)

        # 发电侧结果表格
        if result.plantResultCount > 0:
            plant_table = QTableWidget(result.plantResultCount, 3)
            plant_table.setHorizontalHeaderLabels(["电厂名称", "出清电量（MW）", "收入（元）"])
            for i in range(result.plantResultCount):
                # 安全解析：避免编码错误
                plant_name = f"电厂{i + 1}"
                try:
                    if result.plantNames and result.plantNames[i]:
                        # 使用 string_at 安全获取字节数据
                        from ctypes import string_at
                        plant_name_bytes = string_at(result.plantNames[i])
                        # 尝试多种解码方式
                        try:
                            plant_name = plant_name_bytes.decode('utf-8')
                        except UnicodeDecodeError:
                            try:
                                plant_name = plant_name_bytes.decode('gbk', errors='ignore')
                            except:
                                plant_name = plant_name_bytes.decode('latin-1', errors='ignore')
                except:
                    plant_name = f"电厂{i + 1}"

                cleared_p = f"{result.plantClearedP[i]:.2f}" if result.plantClearedP and i < result.plantResultCount else "0.00"
                income = f"{result.plantIncome[i]:.2f}" if result.plantIncome and i < result.plantResultCount else "0.00"

                plant_table.setItem(i, 0, QTableWidgetItem(plant_name))
                plant_table.setItem(i, 1, QTableWidgetItem(cleared_p))
                plant_table.setItem(i, 2, QTableWidgetItem(income))
            left_layout.addWidget(plant_table)
        else:
            left_layout.addWidget(QLabel("无发电侧出清结果"))

        # 用户侧结果表格
        if result.userResultCount > 0:
            user_table = QTableWidget(result.userResultCount, 3)
            user_table.setHorizontalHeaderLabels(["用户名称", "出清负荷（MW）", "账单（元）"])
            for i in range(result.userResultCount):
                # 安全解析：避免编码错误
                user_name = f"用户{i + 1}"
                try:
                    if result.userNames and result.userNames[i]:
                        # 使用 string_at 安全获取字节数据
                        from ctypes import string_at
                        user_name_bytes = string_at(result.userNames[i])
                        # 尝试多种解码方式
                        try:
                            user_name = user_name_bytes.decode('utf-8')
                        except UnicodeDecodeError:
                            try:
                                user_name = user_name_bytes.decode('gbk', errors='ignore')
                            except:
                                user_name = user_name_bytes.decode('latin-1', errors='ignore')
                except:
                    user_name = f"用户{i + 1}"

                cleared_p = f"{result.userClearedP[i]:.2f}" if result.userClearedP and i < result.userResultCount else "0.00"
                bill = f"{result.userBill[i]:.2f}" if result.userBill and i < result.userResultCount else "0.00"

                user_table.setItem(i, 0, QTableWidgetItem(user_name))
                user_table.setItem(i, 1, QTableWidgetItem(cleared_p))
                user_table.setItem(i, 2, QTableWidgetItem(bill))
            left_layout.addWidget(user_table)
        else:
            left_layout.addWidget(QLabel("无用户侧出清结果"))

        # 右侧：供需曲线图片
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)

        # 加载并显示图片
        if img_path and os.path.exists(img_path):
            try:
                pixmap = QPixmap(img_path)
                if not pixmap.isNull():
                    # 缩放图片以适应窗口
                    scaled_pixmap = pixmap.scaled(600, 400, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    image_label = QLabel()
                    image_label.setPixmap(scaled_pixmap)
                    right_layout.addWidget(image_label)
                else:
                    right_layout.addWidget(QLabel("图片加载失败"))
            except Exception as e:
                print(f"图片加载错误: {e}")
                right_layout.addWidget(QLabel("图片加载失败"))
        else:
            right_layout.addWidget(QLabel("图片文件不存在"))

        # 将左右两部分添加到主布局
        main_layout.addWidget(left_widget, 1)
        main_layout.addWidget(right_widget, 1)

        result_window.setLayout(main_layout)
        result_window.exec_()

# 发电侧报价窗口
        class PlantBidWindow(QDialog):
            def __init__(self, parent):
                super().__init__(parent)
                self.submitted_data = None
                self.setWindowTitle("发电侧阶梯报价录入")
                self.setGeometry(200, 200, 800, 500)  # 增加窗口大小

                layout = QVBoxLayout()

                # 电厂名称
                self.plant_name_edit = QLineEdit()
                self.plant_name_edit.setPlaceholderText("电厂名称")
                layout.addWidget(QLabel("电厂名称:"))
                layout.addWidget(self.plant_name_edit)

                # 机组数量选择
                self.unit_count_spin = QSpinBox()
                self.unit_count_spin.setRange(1, 10)
                self.unit_count_spin.setValue(1)
                self.unit_count_spin.valueChanged.connect(self.updateUnitTabs)
                layout.addWidget(QLabel("机组数量:"))
                layout.addWidget(self.unit_count_spin)

                # 机组标签页
                self.tab_widget = QTabWidget()
                layout.addWidget(self.tab_widget)

                # 初始化第一个机组标签页
                self.unit_tabs = []
                self.updateUnitTabs(1)

                # 提交按钮
                submit_btn = QPushButton("提交")
                submit_btn.clicked.connect(self.submitBid)
                layout.addWidget(submit_btn)

                self.setLayout(layout)

            def updateUnitTabs(self, count):
                """根据机组数量更新标签页"""
                # 移除所有现有标签页
                while self.tab_widget.count() > 0:
                    self.tab_widget.removeTab(0)

                self.unit_tabs = []

                # 创建新的标签页
                for i in range(count):
                    tab = QWidget()
                    layout = QVBoxLayout(tab)

                    # 机组编号
                    unit_no_label = QLabel(f"机组 #{i + 1} 编号:")
                    unit_no_edit = QLineEdit()
                    unit_no_edit.setPlaceholderText(f"机组#{i + 1}编号")
                    layout.addWidget(unit_no_label)
                    layout.addWidget(unit_no_edit)

                    # 报价段数
                    segment_label = QLabel("报价段数 (1-10):")
                    segment_spin = QSpinBox()
                    segment_spin.setRange(1, 10)
                    segment_spin.setValue(2)
                    layout.addWidget(segment_label)
                    layout.addWidget(segment_spin)

                    # 报价表格
                    table = QTableWidget(2, 2)
                    table.setHorizontalHeaderLabels(["出力段 (MW)", "报价段 (元/MWh)"])
                    layout.addWidget(table)

                    # 动态调整行数
                    segment_spin.valueChanged.connect(lambda value, t=table: t.setRowCount(value))

                    # 存储控件引用
                    self.unit_tabs.append({
                        'unit_no_edit': unit_no_edit,
                        'segment_spin': segment_spin,
                        'table': table
                    })

                    self.tab_widget.addTab(tab, f"机组 #{i + 1}")

            def submitBid(self):
                plant_name_text = self.plant_name_edit.text().strip()
                if not plant_name_text:
                    QMessageBox.warning(self, "错误", "电厂名称不能为空!")
                    return

                units_data = []

                # 收集所有机组数据
                for i, tab_data in enumerate(self.unit_tabs):
                    unit_no_text = tab_data['unit_no_edit'].text().strip()
                    if not unit_no_text:
                        QMessageBox.warning(self, "错误", f"机组 #{i + 1} 编号不能为空!")
                        return

                    segment_count = tab_data['segment_spin'].value()
                    P = []
                    C = []

                    # 收集表格数据
                    table = tab_data['table']
                    for row in range(segment_count):
                        p_item = table.item(row, 0)
                        c_item = table.item(row, 1)
                        if not p_item or not c_item or not p_item.text().strip() or not c_item.text().strip():
                            QMessageBox.warning(self, "错误", f"机组 #{i + 1} 第 {row + 1} 行数据不完整!")
                            return
                        try:
                            P.append(float(p_item.text().strip()))
                            C.append(float(c_item.text().strip()))
                        except ValueError:
                            QMessageBox.warning(self, "错误", f"机组 #{i + 1} 第 {row + 1} 行需输入有效数字!")
                            return

                    # 报价单调性校验
                    if any(C[i] < C[i - 1] for i in range(1, len(C))):
                        QMessageBox.warning(self, "错误", f"机组 #{i + 1} 报价需单调非递减!")
                        return

                    units_data.append({
                        "unitNo": unit_no_text,
                        "segmentCount": segment_count,
                        "P": P,
                        "C": C
                    })

                self.submitted_data = {
                    "plantName": plant_name_text,
                    "units": units_data  # 存储所有机组数据
                }

                # 提交到C++库
                plant_name_bytes = plant_name_text.encode('utf-8')
                plant_name_c = cast(create_string_buffer(plant_name_bytes), c_char_p)

                # 为每个机组创建C结构体
                for unit_data in units_data:
                    segment_count = unit_data["segmentCount"]
                    P_arr = (c_double * segment_count)(*unit_data["P"])
                    C_arr = (c_double * segment_count)(*unit_data["C"])

                    unit_no_bytes = unit_data["unitNo"].encode('utf-8')
                    unit_no_c = cast(create_string_buffer(unit_no_bytes), c_char_p)

                    bid = PlantBid_C()
                    bid.plantName = plant_name_c
                    bid.unitNo = unit_no_c
                    bid.segmentCount = segment_count
                    bid.P = P_arr
                    bid.C = C_arr

                    lib.submitPlantBid(byref(bid))

                self.accept()
                QMessageBox.information(self, "成功", f"发电侧申报单提交成功! 共{len(units_data)}个机组")

            def get_submitted_data(self):
                return self.submitted_data

                # 用户侧报价窗口

        class UserBidWindow(QDialog):
            def __init__(self, parent):
                super().__init__(parent)
                self.submitted_data = None
                self.setWindowTitle("用户侧阶梯报价录入")
                self.setGeometry(200, 200, 600, 400)

                layout = QVBoxLayout()

                # 用户名称、编号
                self.user_name_edit = QLineEdit()
                self.user_name_edit.setPlaceholderText("用户名称")
                self.user_no_edit = QLineEdit()
                self.user_no_edit.setPlaceholderText("用户编号（如U1）")
                layout.addWidget(self.user_name_edit)
                layout.addWidget(self.user_no_edit)

                # 报价段数（1~10）
                self.segment_spin = QSpinBox()
                self.segment_spin.setRange(1, 10)
                self.segment_spin.setValue(2)
                layout.addWidget(QLabel("报价段数（1~10）："))
                layout.addWidget(self.segment_spin)

                # 负荷、报价表格
                self.table = QTableWidget(2, 2)
                self.table.setHorizontalHeaderLabels(["负荷段（MW）", "报价段（元/MWh）"])
                layout.addWidget(self.table)

                # 动态调整行数
                self.segment_spin.valueChanged.connect(self.updateTableRows)

                # 提交按钮（带单调非递增校验）
                submit_btn = QPushButton("提交")
                submit_btn.clicked.connect(self.submitBid)
                layout.addWidget(submit_btn)

                self.setLayout(layout)

            def updateTableRows(self, value):
                self.table.setRowCount(value)
                # 新增行后，为新行的单元格设置默认值
                for row in range(self.table.rowCount()):
                    for col in range(self.table.columnCount()):
                        if not self.table.item(row, col):
                            self.table.setItem(row, col, QTableWidgetItem(""))

            def submitBid(self):
                user_name_text = self.user_name_edit.text().strip()
                user_no_text = self.user_no_edit.text().strip()
                if not user_name_text or not user_no_text:
                    QMessageBox.warning(self, "错误", "用户名称和编号不能为空！")
                    return

                segment_count = self.segment_spin.value()
                P = []
                C = []
                # 表格数据校验
                for i in range(segment_count):
                    p_item = self.table.item(i, 0)
                    c_item = self.table.item(i, 1)
                    if not p_item or not c_item or not p_item.text().strip() or not c_item.text().strip():
                        QMessageBox.warning(self, "错误", f"第{i + 1}行数据不完整！")
                        return
                    try:
                        P.append(float(p_item.text().strip()))
                        C.append(float(c_item.text().strip()))
                    except ValueError:
                        QMessageBox.warning(self, "错误", f"第{i + 1}行需输入有效数字！")
                        return

                # 报价单调性校验
                if any(C[i] > C[i - 1] for i in range(1, len(C))):
                    QMessageBox.warning(self, "错误", "用户侧报价需单调非递增！")
                    return
                self.submitted_data = {
                    "userName": user_name_text,  # 用户名称
                    "userNo": user_no_text,  # 用户编号
                    "segmentCount": segment_count,  # 报价段数
                    "P": P,  # 负荷段列表（[float, ...]）
                    "C": C  # 报价段列表（[float, ...]）
                }

        user_name_bytes = user_name_text.encode('utf-8')
        user_no_bytes = user_no_text.encode('utf-8')
        user_name_c = cast(create_string_buffer(user_name_bytes), c_char_p)
        user_no_c = cast(create_string_buffer(user_no_bytes), c_char_p)

        # 构造C风格数组
        P_arr = (c_double * segment_count)(*P)
        C_arr = (c_double * segment_count)(*C)

        # 构造结构体
        bid = UserBid_C()
        bid.userName = user_name_c  # 改这里：用cast后的c_char_p
        bid.userNo = user_no_c  # 改这里：用cast后的c_char_p
        bid.segmentCount = segment_count
        bid.P = P_arr
        bid.C = C_arr

        # 调用动态库
        lib.submitUserBid(byref(bid))
        self.accept()  # 先结束模态，再弹窗
        QMessageBox.information(self, "成功", "用户侧申报单提交成功！")

    def get_submitted_data(self):
        return self.submitted_data


# 启动PyQt事件循环
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()  # 显示主窗口（之前代码遗漏，补充关键步骤）

    # 处理程序退出逻辑：关闭前调用C++侧initMarket释放所有申报数据内存
    def on_app_quit():
        lib.initMarket()  # 复用initMarket的内存释放逻辑，清空plants和users数组
        print("程序退出，已释放所有申报数据内存")

    # 绑定程序退出信号，确保内存彻底释放
    app.aboutToQuit.connect(on_app_quit)

    # 启动事件循环，捕获退出码
    sys.exit(app.exec_())


'''下面是改进的整合完整代码

import os
import matplotlib

matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
import sys
import hashlib
from ctypes import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import numpy as np
import pandas as pd

# 设置中文字体
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False

# 加载macOS动态库（需根据实际路径修改）
lib = CDLL('/Users/rose/python_/cpp_core/libmarket_clearing.dylib')


# -------------------------- 1. 账号数据模型（未修改）--------------------------
class User:
    def __init__(self, account, pwd_hash, role, org_name=None):
        self.account = account  # 唯一账号
        self.pwd_hash = pwd_hash  # 密码哈希（非明文）
        self.role = role  # 角色：plant_admin/plant_op/user_admin/user_op
        self.org_name = org_name  # 所属机构（电厂/用户名称）

    def verify_pwd(self, input_pwd):
        """密码校验：输入明文 → 加盐哈希 → 对比存储哈希"""
        input_hash = hashlib.md5((input_pwd + "power_market_salt").encode()).hexdigest()
        return input_hash == self.pwd_hash


def init_default_users():
    """初始化默认账号库（实际项目可替换为数据库查询）"""

    def get_pwd_hash(pwd):
        return hashlib.md5((pwd + "power_market_salt").encode()).hexdigest()

    # 默认账号列表：(账号, 明文密码, 角色, 所属机构)
    default_users = [
        ("plant_admin", "Plant@2024", "plant_admin", "全网发电管理员"),
        ("plant_op_1", "Op1@Plant", "plant_op", "华东电厂"),
        ("plant_op_2", "Op2@Plant", "plant_op", "华北电厂"),
        ("user_admin", "User@2024", "user_admin", "全网用户管理员"),
        ("user_op_1", "Op1@User", "user_op", "工业用户A"),
        ("user_op_2", "Op2@User", "user_op", "商业用户B")
    ]

    return [User(acc, get_pwd_hash(pwd), role, org) for acc, pwd, role, org in default_users]


# 全局账号库（程序启动时初始化）
GLOBAL_USER_DB = init_default_users()


# -------------------------- 2. C风格结构体定义（原有）--------------------------
class PlantBid_C(Structure):
    _fields_ = [
        ("plantName", c_char_p),
        ("unitNo", c_char_p),
        ("segmentCount", c_int),
        ("P", POINTER(c_double)),
        ("C", POINTER(c_double))
    ]


class UserBid_C(Structure):
    _fields_ = [
        ("userName", c_char_p),
        ("userNo", c_char_p),
        ("segmentCount", c_int),
        ("P", POINTER(c_double)),
        ("C", POINTER(c_double))
    ]


class ClearingResult_C(Structure):
    _fields_ = [
        ("clearingPrice", c_double),
        ("totalClearedP", c_double),
        ("plantResultCount", c_int),
        ("plantNames", POINTER(c_char_p)),
        ("plantClearedP", POINTER(c_double)),
        ("plantIncome", POINTER(c_double)),
        ("userResultCount", c_int),
        ("userNames", POINTER(c_char_p)),
        ("userClearedP", POINTER(c_double)),
        ("userBill", POINTER(c_double))
    ]


# 初始化动态库
lib.initMarket()
print("C++动态库加载成功！")


# -------------------------- 3. 登录窗口（未修改）--------------------------
class LoginWindow(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.current_user = None  # 登录成功后存储当前用户对象
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("电力市场账号登录")
        self.setGeometry(300, 300, 450, 320)
        self.setFixedSize(450, 320)

        # 主布局
        main_layout = QVBoxLayout()
        main_layout.setSpacing(15)
        main_layout.setContentsMargins(60, 40, 60, 30)

        # 标题
        title_label = QLabel("电力现货市场出清平台", self)
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #2C3E50;")
        main_layout.addWidget(title_label)

        # 账号输入
        acc_layout = QHBoxLayout()
        acc_icon = QLabel("账号：", self)
        acc_icon.setStyleSheet("font-size: 13px;")
        self.account_edit = QLineEdit(self)
        self.account_edit.setPlaceholderText("请输入登录账号")
        self.account_edit.setStyleSheet("padding: 9px; font-size: 13px; border: 1px solid #BDC3C7; border-radius: 4px;")
        acc_layout.addWidget(acc_icon)
        acc_layout.addWidget(self.account_edit)
        main_layout.addLayout(acc_layout)

        # 密码输入（带显示/隐藏切换）
        pwd_layout = QHBoxLayout()
        pwd_icon = QLabel("密码：", self)
        pwd_icon.setStyleSheet("font-size: 13px;")
        self.pwd_edit = QLineEdit(self)
        self.pwd_edit.setPlaceholderText("请输入密码")
        self.pwd_edit.setEchoMode(QLineEdit.Password)
        self.pwd_edit.setStyleSheet("padding: 9px; font-size: 13px; border: 1px solid #BDC3C7; border-radius: 4px;")
        self.pwd_toggle_btn = QPushButton("👁️", self)
        self.pwd_toggle_btn.setFixedSize(30, 34)
        self.pwd_toggle_btn.setStyleSheet("border: none; background: transparent;")
        self.pwd_toggle_btn.clicked.connect(self.toggle_pwd_visible)
        pwd_layout.addWidget(pwd_icon)
        pwd_layout.addWidget(self.pwd_edit)
        pwd_layout.addWidget(self.pwd_toggle_btn)
        main_layout.addLayout(pwd_layout)

        # 记住密码
        self.remember_pwd = QCheckBox("记住本次登录（7天内有效）", self)
        self.remember_pwd.setStyleSheet("font-size: 12px; color: #7F8C8D;")
        main_layout.addWidget(self.remember_pwd)

        # 登录按钮
        self.login_btn = QPushButton("登录", self)
        self.login_btn.setStyleSheet(
            "padding: 10px; font-size: 14px; background-color: #3498DB; color: white; border: none; border-radius: 4px;")
        self.login_btn.clicked.connect(self.check_login)
        main_layout.addWidget(self.login_btn)

        # 角色提示
        self.role_tip = QLabel("", self)
        self.role_tip.setAlignment(Qt.AlignCenter)
        self.role_tip.setStyleSheet("font-size: 12px; color: #27AE60; margin-top: 10px;")
        self.role_tip.hide()
        main_layout.addWidget(self.role_tip)

        self.setLayout(main_layout)

    def toggle_pwd_visible(self):
        """切换密码显示状态"""
        if self.pwd_edit.echoMode() == QLineEdit.Password:
            self.pwd_edit.setEchoMode(QLineEdit.Normal)
            self.pwd_toggle_btn.setText("🙈")
        else:
            self.pwd_edit.setEchoMode(QLineEdit.Password)
            self.pwd_toggle_btn.setText("👁️")

    def check_login(self):
        """完整登录校验逻辑"""
        account = self.account_edit.text().strip()
        input_pwd = self.pwd_edit.text().strip()

        # 1. 空值校验
        if not account or not input_pwd:
            QMessageBox.warning(self, "输入错误", "账号和密码不能为空！")
            return

        # 2. 账号存在性校验
        target_user = None
        for user in GLOBAL_USER_DB:
            if user.account == account:
                target_user = user
                break
        if not target_user:
            QMessageBox.warning(self, "账号错误", "该账号未注册，请检查输入！")
            return

        # 3. 密码正确性校验
        if not target_user.verify_pwd(input_pwd):
            QMessageBox.warning(self, "密码错误", "输入的密码不正确，请重试！")
            return

        # 4. 角色提示
        role_map = {
            "plant_admin": "发电侧管理员（可提交/编辑所有电厂报价）",
            "plant_op": f"发电侧操作员（仅可提交{target_user.org_name}的报价）",
            "user_admin": "用户侧管理员（可提交/编辑所有用户报价）",
            "user_op": f"用户侧操作员（仅可提交{target_user.org_name}的报价）"
        }
        self.role_tip.setText(f"登录成功！当前角色：{role_map[target_user.role]}")
        self.role_tip.show()

        # 5. 记住登录状态
        if self.remember_pwd.isChecked():
            self.save_login_state(target_user.account)

        # 6. 记录用户并关闭窗口
        self.current_user = target_user
        QTimer.singleShot(1000, self.accept)

    def save_login_state(self, account):
        """保存登录状态到本地"""
        settings = QSettings("PowerMarketApp", "LoginState")
        settings.setValue("last_account", account)
        settings.setValue("expire_date", QDate.currentDate().addDays(7).toString("yyyy-MM-dd"))
        print(f"已保存登录状态，账号{account}有效期至{settings.value('expire_date')}")

    def get_current_user(self):
        """返回当前登录用户对象"""
        return self.current_user


# -------------------------- 4. 发电侧报价窗口（未修改）--------------------------
class PlantBidWindow(QDialog):
    def __init__(self, parent, logged_user):
        super().__init__(parent)
        self.logged_user = logged_user  # 当前登录用户
        self.submitted_data = None
        self.setWindowTitle("发电侧阶梯报价录入")
        self.setGeometry(200, 200, 800, 500)
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # 电厂名称（操作员默认填充所属机构，不可修改）
        self.plant_name_edit = QLineEdit()
        if self.logged_user.role == "plant_op":
            self.plant_name_edit.setText(self.logged_user.org_name)
            self.plant_name_edit.setReadOnly(True)  # 操作员不可修改电厂名称
        else:
            self.plant_name_edit.setPlaceholderText("电厂名称")
        layout.addWidget(QLabel("电厂名称:"))
        layout.addWidget(self.plant_name_edit)

        # 机组数量选择
        self.unit_count_spin = QSpinBox()
        self.unit_count_spin.setRange(1, 10)
        self.unit_count_spin.setValue(1)
        self.unit_count_spin.valueChanged.connect(self.updateUnitTabs)
        layout.addWidget(QLabel("机组数量:"))
        layout.addWidget(self.unit_count_spin)

        # 机组标签页
        self.tab_widget = QTabWidget()
        layout.addWidget(self.tab_widget)
        self.unit_tabs = []
        self.updateUnitTabs(1)

        # 提交按钮
        submit_btn = QPushButton("提交")
        submit_btn.clicked.connect(self.submitBid)
        layout.addWidget(submit_btn)

        self.setLayout(layout)

    def updateUnitTabs(self, count):
        """更新机组标签页"""
        while self.tab_widget.count() > 0:
            self.tab_widget.removeTab(0)
        self.unit_tabs = []

        for i in range(count):
            tab = QWidget()
            layout = QVBoxLayout(tab)

            # 机组编号
            unit_no_label = QLabel(f"机组 #{i + 1} 编号:")
            unit_no_edit = QLineEdit()
            unit_no_edit.setPlaceholderText(f"机组#{i + 1}编号")
            layout.addWidget(unit_no_label)
            layout.addWidget(unit_no_edit)

            # 报价段数
            segment_label = QLabel("报价段数 (1-10):")
            segment_spin = QSpinBox()
            segment_spin.setRange(1, 10)
            segment_spin.setValue(2)
            layout.addWidget(segment_label)
            layout.addWidget(segment_spin)

            # 报价表格
            table = QTableWidget(2, 2)
            table.setHorizontalHeaderLabels(["出力段 (MW)", "报价段 (元/MWh)"])
            layout.addWidget(table)
            segment_spin.valueChanged.connect(lambda value, t=table: t.setRowCount(value))

            self.unit_tabs.append({
                'unit_no_edit': unit_no_edit,
                'segment_spin': segment_spin,
                'table': table
            })
            self.tab_widget.addTab(tab, f"机组 #{i + 1}")

    def submitBid(self):
        plant_name_text = self.plant_name_edit.text().strip()
        if not plant_name_text:
            QMessageBox.warning(self, "错误", "电厂名称不能为空!")
            return

        # 操作员权限校验：仅能提交所属机构报价
        if self.logged_user.role == "plant_op" and plant_name_text != self.logged_user.org_name:
            QMessageBox.warning(
                self, "权限不足",
                f"您是{self.logged_user.org_name}的操作员，仅能提交该电厂的报价！"
            )
            return

        units_data = []
        for i, tab_data in enumerate(self.unit_tabs):
            unit_no_text = tab_data['unit_no_edit'].text().strip()
            if not unit_no_text:
                QMessageBox.warning(self, "错误", f"机组 #{i + 1} 编号不能为空!")
                return

            segment_count = tab_data['segment_spin'].value()
            P = []
            C = []
            table = tab_data['table']
            for row in range(segment_count):
                p_item = table.item(row, 0)
                c_item = table.item(row, 1)
                if not p_item or not c_item or not p_item.text().strip() or not c_item.text().strip():
                    QMessageBox.warning(self, "错误", f"机组 #{i + 1} 第 {row + 1} 行数据不完整!")
                    return
                try:
                    P.append(float(p_item.text().strip()))
                    C.append(float(c_item.text().strip()))
                except ValueError:
                    QMessageBox.warning(self, "错误", f"机组 #{i + 1} 第 {row + 1} 行需输入有效数字!")
                    return

            # 报价单调性校验
            if any(C[i] < C[i - 1] for i in range(1, len(C))):
                QMessageBox.warning(self, "错误", f"机组 #{i + 1} 报价需单调非递减!")
                return

            units_data.append({
                "unitNo": unit_no_text,
                "segmentCount": segment_count,
                "P": P,
                "C": C
            })

        # 存储提交数据
        self.submitted_data = {
            "plantName": plant_name_text,
            "units": units_data
        }

        # 提交到C++库
        plant_name_bytes = plant_name_text.encode('utf-8')
        plant_name_c = cast(create_string_buffer(plant_name_bytes), c_char_p)
        for unit_data in units_data:
            segment_count = unit_data["segmentCount"]
            P_arr = (c_double * segment_count)(*unit_data["P"])
            C_arr = (c_double * segment_count)(*unit_data["C"])
            unit_no_bytes = unit_data["unitNo"].encode('utf-8')
            unit_no_c = cast(create_string_buffer(unit_no_bytes), c_char_p)

            bid = PlantBid_C()
            bid.plantName = plant_name_c
            bid.unitNo = unit_no_c
            bid.segmentCount = segment_count
            bid.P = P_arr
            bid.C = C_arr
            lib.submitPlantBid(byref(bid))

        self.accept()
        QMessageBox.information(self, "成功", f"发电侧申报单提交成功! 共{len(units_data)}个机组")

    def get_submitted_data(self):
        return self.submitted_data


# -------------------------- 5. 用户侧报价窗口（未修改）--------------------------
class UserBidWindow(QDialog):
    def __init__(self, parent, logged_user):
        super().__init__(parent)
        self.logged_user = logged_user  # 当前登录用户
        self.submitted_data = None
        self.setWindowTitle("用户侧阶梯报价录入")
        self.setGeometry(200, 200, 600, 400)
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # 用户名称（操作员默认填充所属机构，不可修改）
        self.user_name_edit = QLineEdit()
        if self.logged_user.role == "user_op":
            self.user_name_edit.setText(self.logged_user.org_name)
            self.user_name_edit.setReadOnly(True)  # 操作员不可修改用户名
        else:
            self.user_name_edit.setPlaceholderText("用户名称")
        layout.addWidget(self.user_name_edit)

        # 用户编号
        self.user_no_edit = QLineEdit()
        self.user_no_edit.setPlaceholderText("用户编号（如U1）")
        layout.addWidget(self.user_no_edit)

        # 报价段数
        self.segment_spin = QSpinBox()
        self.segment_spin.setRange(1, 10)
        self.segment_spin.setValue(2)
        layout.addWidget(QLabel("报价段数（1~10）："))
        layout.addWidget(self.segment_spin)

        # 报价表格
        self.table = QTableWidget(2, 2)
        self.table.setHorizontalHeaderLabels(["负荷段（MW）", "报价段（元/MWh）"])
        layout.addWidget(self.table)
        self.segment_spin.valueChanged.connect(self.updateTableRows)

        # 提交按钮
        submit_btn = QPushButton("提交")
        submit_btn.clicked.connect(self.submitBid)
        layout.addWidget(submit_btn)

        self.setLayout(layout)

    def updateTableRows(self, value):
        self.table.setRowCount(value)
        for row in range(self.table.rowCount()):
            for col in range(self.table.columnCount()):
                if not self.table.item(row, col):
                    self.table.setItem(row, col, QTableWidgetItem(""))

    def submitBid(self):
        user_name_text = self.user_name_edit.text().strip()
        user_no_text = self.user_no_edit.text().strip()
        if not user_name_text or not user_no_text:
            QMessageBox.warning(self, "错误", "用户名称和编号不能为空！")
            return

        # 操作员权限校验：仅能提交所属机构报价
        if self.logged_user.role == "user_op" and user_name_text != self.logged_user.org_name:
            QMessageBox.warning(
                self, "权限不足",
                f"您是{self.logged_user.org_name}的操作员，仅能提交该用户的报价！"
            )
            return

        segment_count = self.segment_spin.value()
        P = []
        C = []
        for i in range(segment_count):
            p_item = self.table.item(i, 0)
            c_item = self.table.item(i, 1)
            if not p_item or not c_item or not p_item.text().strip() or not c_item.text().strip():
                QMessageBox.warning(self, "错误", f"第{i + 1}行数据不完整！")
                return
            try:
                P.append(float(p_item.text().strip()))
                C.append(float(c_item.text().strip()))
            except ValueError:
                QMessageBox.warning(self, "错误", f"第{i + 1}行需输入有效数字！")
                return

        # 报价单调性校验
        if any(C[i] > C[i - 1] for i in range(1, len(C))):
            QMessageBox.warning(self, "错误", "用户侧报价需单调非递增！")
            return

        # 存储提交数据
        self.submitted_data = {
            "userName": user_name_text,
            "userNo": user_no_text,
            "segmentCount": segment_count,
            "P": P,
            "C": C
        }

        # 提交到C++库
        user_name_bytes = user_name_text.encode('utf-8')
        user_no_bytes = user_no_text.encode('utf-8')
        user_name_c = cast(create_string_buffer(user_name_bytes), c_char_p)
        user_no_c = cast(create_string_buffer(user_no_bytes), c_char_p)
        P_arr = (c_double * segment_count)(*P)
        C_arr = (c_double * segment_count)(*C)

        bid = UserBid_C()
        bid.userName = user_name_c
        bid.userNo = user_no_c
        bid.segmentCount = segment_count
        bid.P = P_arr
        bid.C = C_arr
        lib.submitUserBid(byref(bid))

        self.accept()
        QMessageBox.information(self, "成功", "用户侧申报单提交成功！")

    def get_submitted_data(self):
        return self.submitted_data


# -------------------------- 6. 主窗口（核心错误修复）--------------------------
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("电力现货市场出清仿真平台")
        self.setGeometry(100, 100, 800, 600)
        self.plants = []  # 电厂报价数据
        self.users = []  # 用户报价数据
        self.logged_user = None  # 当前登录用户（初始为None）

        # 强制macOS菜单栏显示在窗口内
        self.menuBar().setNativeMenuBar(False)

        # 初始化菜单（修复：提前定义Action，避免后续引用错误）
        self.init_menu_actions()
        self.init_menus()

        # 中心布局
        self.main_widget = QWidget()
        self.main_layout = QVBoxLayout()
        self.main_widget.setLayout(self.main_layout)
        self.setCentralWidget(self.main_widget)

        # 状态栏
        self.status_label = QLabel("等待操作...")
        self.main_layout.addWidget(self.status_label)

        # 自动恢复登录状态
        self.restore_login_state()

    def init_menu_actions(self):
        """提前定义所有菜单Action（避免后续重复创建）"""
        # 发电侧相关Action
        self.plant_login_action = QAction("发电侧账号登录", self)
        self.plant_login_action.triggered.connect(self.plant_side_login)
        self.plant_bid_action = QAction("提交电厂报价", self)
        self.plant_bid_action.triggered.connect(self.openPlantBidWindow)

        # 用户侧相关Action
        self.user_login_action = QAction("用户侧账号登录", self)
        self.user_login_action.triggered.connect(self.user_side_login)
        self.user_bid_action = QAction("提交用户报价", self)
        self.user_bid_action.triggered.connect(self.openUserBidWindow)

        # 导入数据相关Action（修复：selfq → self）
        self.import_plant_action = QAction("导入发电侧数据", self)
        self.import_plant_action.triggered.connect(self.importPlantData)
        self.import_user_action = QAction("导入用户侧数据", self)
        self.import_user_action.triggered.connect(self.importUserData)

        # 交易中心相关Action
        self.clear_market_action = QAction("执行市场出清", self)
        self.clear_market_action.triggered.connect(self.clearMarket)

        # 系统相关Action
        self.logout_action = QAction("退出登录", self)
        self.logout_action.triggered.connect(self.logout)

    def init_menus(self):
        """初始化菜单栏：含登录、报价、导入、出清、登出功能（修复Action显示/隐藏逻辑）"""
        menu_bar = self.menuBar()

        # 1. 发电侧菜单（初始仅显示登录，不显示报价）
        self.plant_menu = menu_bar.addMenu("发电侧")
        self.plant_menu.addAction(self.plant_login_action)
        # 报价Action初始不添加到菜单（登录后再添加）

        # 2. 用户侧菜单（初始仅显示登录，不显示报价）
        self.user_menu = menu_bar.addMenu("用户侧")
        self.user_menu.addAction(self.user_login_action)
        # 报价Action初始不添加到菜单（登录后再添加）

        # 3. 导入数据菜单（正常显示）
        self.import_menu = menu_bar.addMenu("导入数据")
        self.import_menu.addAction(self.import_plant_action)
        self.import_menu.addAction(self.import_user_action)

        # 4. 交易中心菜单（正常显示）
        self.trade_center_menu = menu_bar.addMenu("交易中心")
        self.trade_center_menu.addAction(self.clear_market_action)

        # 5. 系统菜单（初始不显示登出，登录后再添加）
        self.system_menu = menu_bar.addMenu("系统")

    def restore_login_state(self):
        """从本地恢复登录状态（检查有效期）"""
        settings = QSettings("PowerMarketApp", "LoginState")
        last_account = settings.value("last_account")
        expire_date_str = settings.value("expire_date")

        # 无记录或已过期
        if not last_account or not expire_date_str:
            return
        expire_date = QDate.fromString(expire_date_str, "yyyy-MM-dd")
        if QDate.currentDate() > expire_date:
            settings.clear()
            return

        # 查找账号并恢复登录
        target_user = None
        for user in GLOBAL_USER_DB:
            if user.account == last_account:
                target_user = user
                break
        if not target_user:
            return

        # 恢复角色对应的菜单（修复：添加报价和登出Action到菜单）
        self.logged_user = target_user
        if target_user.role in ["plant_admin", "plant_op"]:
            self.plant_menu.addAction(self.plant_bid_action)  # 登录后添加报价Action
        elif target_user.role in ["user_admin", "user_op"]:
            self.user_menu.addAction(self.user_bid_action)  # 登录后添加报价Action
        self.system_menu.addAction(self.logout_action)  # 登录后添加登出Action
        self.status_label.setText(f"自动登录：{target_user.org_name}（{target_user.role}）")

    def plant_side_login(self):
        """发电侧登录逻辑：仅允许发电侧角色登录（修复菜单Action添加逻辑）"""
        login_window = LoginWindow(self)
        if login_window.exec_() != QDialog.Accepted:
            return
        self.logged_user = login_window.get_current_user()

        # 角色校验
        if self.logged_user.role not in ["plant_admin", "plant_op"]:
            QMessageBox.warning(self, "角色错误", "该账号不属于发电侧，无法使用发电侧功能！")
            self.logged_user = None
            return

        # 登录成功：添加报价和登出Action到菜单（避免重复添加）
        if self.plant_bid_action not in self.plant_menu.actions():
            self.plant_menu.addAction(self.plant_bid_action)
        if self.logout_action not in self.system_menu.actions():
            self.system_menu.addAction(self.logout_action)
        self.status_label.setText(f"当前登录：{self.logged_user.org_name}（{self.logged_user.role}）")

    def user_side_login(self):
        """用户侧登录逻辑：仅允许用户侧角色登录（修复菜单Action添加逻辑）"""
        login_window = LoginWindow(self)
        if login_window.exec_() != QDialog.Accepted:
            return
        self.logged_user = login_window.get_current_user()

        # 角色校验
        if self.logged_user.role not in ["user_admin", "user_op"]:
            QMessageBox.warning(self, "角色错误", "该账号不属于用户侧，无法使用用户侧功能！")
            self.logged_user = None
            return

        # 登录成功：添加报价和登出Action到菜单（避免重复添加）
        if self.user_bid_action not in self.user_menu.actions():
            self.user_menu.addAction(self.user_bid_action)
        if self.logout_action not in self.system_menu.actions():
            self.system_menu.addAction(self.logout_action)
        self.status_label.setText(f"当前登录：{self.logged_user.org_name}（{self.logged_user.role}）")

    def logout(self):
        """登出功能：清除状态、移除菜单Action（修复Action隐藏逻辑）"""
        if not self.logged_user:
            return
        # 清除本地登录状态
        settings = QSettings("PowerMarketApp", "LoginState")
        settings.clear()
        # 移除报价和登出Action（从菜单中删除，而非hide）
        self.plant_menu.removeAction(self.plant_bid_action)
        self.user_menu.removeAction(self.user_bid_action)
        self.system_menu.removeAction(self.logout_action)
        # 重置状态
        self.logged_user = None
        self.status_label.setText("等待操作...")
        QMessageBox.information(self, "登出成功", "已退出当前账号，可重新登录其他账号！")

    def openPlantBidWindow(self):
        """打开发电侧报价窗口（需先登录）"""
        if not self.logged_user or self.logged_user.role not in ["plant_admin", "plant_op"]:
            QMessageBox.warning(self, "未登录", "请先通过发电侧菜单登录！")
            return
        self.plant_bid_window = PlantBidWindow(self, self.logged_user)
        if self.plant_bid_window.exec_() == QDialog.Accepted:
            plant_data = self.plant_bid_window.get_submitted_data()
            if plant_data:
                self.plants.append(plant_data)
                self.status_label.setText(f"已提交 {len(self.plants)} 个电厂报价（当前登录：{self.logged_user.org_name}）")

    def openUserBidWindow(self):
        """打开用户侧报价窗口（需先登录）"""
        if not self.logged_user or self.logged_user.role not in ["user_admin", "user_op"]:
            QMessageBox.warning(self, "未登录", "请先通过用户侧菜单登录！")
            return
        self.user_bid_window = UserBidWindow(self, self.logged_user)
        if self.user_bid_window.exec_() == QDialog.Accepted:
            user_data = self.user_bid_window.get_submitted_data()
            if user_data:
                self.users.append(user_data)
                self.status_label.setText(f"已提交 {len(self.users)} 个用户报价（当前登录：{self.logged_user.org_name}）")

    # -------------------------- 原有功能：导入数据、出清、绘图（未修改）--------------------------
    def importPlantData(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择发电侧数据文件", "", "CSV Files (*.csv)"
        )
        if not file_path:
            return
        try:
            df = pd.read_csv(file_path)
            plants_data = {}
            for _, row in df.iterrows():
                plant_name = row[0]
                unit_no = row[1]
                segment_count = int(row[2])
                P = []
                C = []
                for i in range(segment_count):
                    P.append(float(row[3 + i * 2]))
                    C.append(float(row[4 + i * 2]))
                if plant_name not in plants_data:
                    plants_data[plant_name] = {"plantName": plant_name, "units": []}
                plants_data[plant_name]["units"].append({
                    "unitNo": unit_no, "segmentCount": segment_count, "P": P, "C": C
                })
            for plant_data in plants_data.values():
                self.plants.append(plant_data)
                plant_name_bytes = plant_data["plantName"].encode('utf-8')
                plant_name_c = cast(create_string_buffer(plant_name_bytes), c_char_p)
                for unit in plant_data["units"]:
                    segment_count = unit["segmentCount"]
                    P_arr = (c_double * segment_count)(*unit["P"])
                    C_arr = (c_double * segment_count)(*unit["C"])
                    unit_no_bytes = unit["unitNo"].encode('utf-8')
                    unit_no_c = cast(create_string_buffer(unit_no_bytes), c_char_p)
                    bid = PlantBid_C()
                    bid.plantName = plant_name_c
                    bid.unitNo = unit_no_c
                    bid.segmentCount = segment_count
                    bid.P = P_arr
                    bid.C = C_arr
                    lib.submitPlantBid(byref(bid))
            QMessageBox.information(self, "成功", f"成功导入 {len(plants_data)} 个电厂数据")
        except Exception as e:
            QMessageBox.warning(self, "错误", f"导入数据失败: {str(e)}")

    def importUserData(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择用户侧数据文件", "", "CSV Files (*.csv)"
        )
        if not file_path:
            return
        try:
            df = pd.read_csv(file_path)
            for _, row in df.iterrows():
                user_name = row[0]
                user_no = row[1]
                segment_count = int(row[2])
                P = []
                C = []
                for i in range(segment_count):
                    P.append(float(row[3 + i * 2]))
                    C.append(float(row[4 + i * 2]))
                user_data = {
                    "userName": user_name, "userNo": user_no, "segmentCount": segment_count, "P": P, "C": C
                }
                self.users.append(user_data)
                user_name_bytes = user_name.encode('utf-8')
                user_no_bytes = user_no.encode('utf-8')
                user_name_c = cast(create_string_buffer(user_name_bytes), c_char_p)
                user_no_c = cast(create_string_buffer(user_no_bytes), c_char_p)
                P_arr = (c_double * segment_count)(*P)
                C_arr = (c_double * segment_count)(*C)
                bid = UserBid_C()
                bid.userName = user_name_c
                bid.userNo = user_no_c
                bid.segmentCount = segment_count
                bid.P = P_arr
                bid.C = C_arr
                lib.submitUserBid(byref(bid))
            QMessageBox.information(self, "成功", f"成功导入 {len(df)} 个用户数据")
        except Exception as e:
            QMessageBox.warning(self, "错误", f"导入数据失败: {str(e)}")

    def clearMarket(self):
        # 1. Python侧计算出清点
        supply_curve = self.calculate_supply_curve()
        demand_curve = self.calculate_demand_curve()
        python_clearing_p, python_clearing_price = self.find_clearing_point(supply_curve, demand_curve)
        print(f"Python计算出清点: {python_clearing_p}MW, {python_clearing_price}元")

        # 2. 调用C++动态库
        lib.clearMarket.argtypes = []
        lib.clearMarket.restype = POINTER(ClearingResult_C)
        result_ptr = lib.clearMarket()
        if not result_ptr:
            QMessageBox.warning(self, "错误", "动态库返回空指针，出清失败！")
            return
        result = result_ptr.contents
        print(f"C++计算出清点: {result.totalClearedP}MW, {result.clearingPrice}元")

        # 3. 用Python结果覆盖C++结果
        result.totalClearedP = python_clearing_p
        result.clearingPrice = python_clearing_price

        # 4. 显示结果
        self.showClearingResult(result)
        self.plot_clearing_curve(python_clearing_p, python_clearing_price, result)

        # 5. 释放内存
        lib.freeClearingResult(result_ptr)

    def showClearingResult(self, result):
        result_window = QDialog()
        result_window.setWindowTitle("出清结果")
        layout = QVBoxLayout()

        # 出清基本信息
        price_label = QLabel(f"出清电价：{result.clearingPrice:.2f} 元/MWh")
        total_p_label = QLabel(f"总出清电量：{result.totalClearedP:.2f} MW")
        layout.addWidget(price_label)
        layout.addWidget(total_p_label)

        # 发电侧结果表格（修复：优先用Python侧存储的中文名称，缩进正确）
        if result.plantResultCount > 0 and len(self.plants) > 0:
            plant_table = QTableWidget(result.plantResultCount, 3)
            plant_table.setHorizontalHeaderLabels(["电厂名称", "出清电量（MW）", "收入（元）"])
            for i in range(result.plantResultCount):
                # 优先从Python侧self.plants取中文名称
                if i < len(self.plants):
                    plant_name = self.plants[i]["plantName"]
                else:
                    # 备用：解析C++返回的名称
                    plant_name = result.plantNames[i].decode('utf-8', errors='replace') if (
                                result.plantNames and result.plantNames[i]) else "未知电厂"

                cleared_p = f"{result.plantClearedP[i]:.2f}" if (
                            result.plantClearedP and i < result.plantResultCount) else "0.00"
                income = f"{result.plantIncome[i]:.2f}" if (
                            result.plantIncome and i < result.plantResultCount) else "0.00"

                plant_table.setItem(i, 0, QTableWidgetItem(plant_name))
                plant_table.setItem(i, 1, QTableWidgetItem(cleared_p))
                plant_table.setItem(i, 2, QTableWidgetItem(income))
            layout.addWidget(plant_table)
        else:
            layout.addWidget(QLabel("无发电侧出清结果"))

        # 用户侧结果表格（修复：优先用Python侧存储的中文名称，缩进正确）
        if result.userResultCount > 0 and len(self.users) > 0:
            user_table = QTableWidget(result.userResultCount, 3)
            user_table.setHorizontalHeaderLabels(["用户名称", "出清负荷（MW）", "账单（元）"])
            for i in range(result.userResultCount):
                # 优先从Python侧self.users取中文名称
                if i < len(self.users):
                    user_name = self.users[i]["userName"]
                else:
                    # 备用：解析C++返回的名称
                    user_name = result.userNames[i].decode('utf-8', errors='replace') if (
                                result.userNames and result.userNames[i]) else "未知用户"

                cleared_p = f"{result.userClearedP[i]:.2f}" if (
                            result.userClearedP and i < result.userResultCount) else "0.00"
                bill = f"{result.userBill[i]:.2f}" if (result.userBill and i < result.userResultCount) else "0.00"

                user_table.setItem(i, 0, QTableWidgetItem(user_name))
                user_table.setItem(i, 1, QTableWidgetItem(cleared_p))
                user_table.setItem(i, 2, QTableWidgetItem(bill))
            layout.addWidget(user_table)
        else:
            layout.addWidget(QLabel("无用户侧出清结果"))

        result_window.setLayout(layout)
        result_window.exec_()

    def calculate_supply_curve(self):
        all_segments = []
        for plant in self.plants:
            if "units" not in plant:
                continue
            for unit in plant["units"]:
                if not unit.get("P") or not unit.get("C"):
                    continue
                seg_count = unit["segmentCount"]
                for i in range(seg_count):
                    p = unit["P"][i]
                    c = unit["C"][i]
                    if p <= 0 or c <= 0:
                        continue
                    all_segments.append((p, c))
        all_segments.sort(key=lambda x: x[1])
        segments = []
        cum_p = 0.0
        for p, c in all_segments:
            cum_p += p
            segments.append((cum_p, c))
        return segments

    def calculate_demand_curve(self):
        all_segments = []
        for user in self.users:
            if not user.get("P") or not user.get("C"):
                continue
            seg_count = user["segmentCount"]
            for i in range(seg_count):
                p = user["P"][i]
                c = user["C"][i]
                if p <= 0 or c <= 0:
                    continue
                all_segments.append((p, c))
        all_segments.sort(key=lambda x: x[1], reverse=True)
        segments = []
        cum_p = 0.0
        for p, c in all_segments:
            cum_p += p
            segments.append((cum_p, c))
        return segments

    def find_clearing_point(self, supply_curve, demand_curve):
        if not supply_curve or not demand_curve:
            return (0.0, 0.0)
        clearing_p = 0.0
        clearing_price = 0.0
        for i in range(len(supply_curve)):
            supply_p, supply_price = supply_curve[i]
            demand_price = 0.0
            for j in range(len(demand_curve)):
                demand_p, temp_demand_price = demand_curve[j]
                if demand_p >= supply_p:
                    demand_price = temp_demand_price
                    break
            if supply_price <= demand_price:
                clearing_p = supply_p
                clearing_price = (supply_price + demand_price) / 2
            else:
                break
        return (clearing_p, clearing_price)

    def plot_clearing_curve(self, clearing_p, clearing_price, result=None):
        supply_curve = self.calculate_supply_curve()
        demand_curve = self.calculate_demand_curve()

        # 解析曲线数据
        def parse_curve(curve):
            if not curve:
                return [0.0, 1.0], [0.0, 0.0]
            x = [0.0]
            y = [curve[0][1]] if curve else [0.0]
            for cum_p, price in curve:
                x.append(x[-1])
                y.append(price)
                x.append(cum_p)
                y.append(price)
            return x, y

        supply_x, supply_y = parse_curve(supply_curve)
        demand_x, demand_y = parse_curve(demand_curve)
        calc_clearing_p, calc_clearing_price = self.find_clearing_point(supply_curve, demand_curve)

        # 绘制图表
        plt.figure(figsize=(12, 8))
        plt.step(supply_x, supply_y, where='post', color='red', label='供应曲线', linewidth=2)
        plt.step(demand_x, demand_y, where='post', color='blue', label='需求曲线', linewidth=2)

        # 绘制出清点
        if calc_clearing_p > 0 and calc_clearing_price > 0:
            plt.scatter(
                calc_clearing_p, calc_clearing_price,
                color='green', s=150, zorder=5, marker='o',
                label=f'出清点({calc_clearing_p:.2f}MW, {calc_clearing_price:.2f}元)'
            )
            plt.axvline(x=calc_clearing_p, color='gray', linestyle='--', alpha=0.7)
            plt.axhline(y=calc_clearing_price, color='gray', linestyle='--', alpha=0.7)

        # 坐标轴设置
        max_x = max(max(supply_x) if supply_x else 0, max(demand_x) if demand_x else 0, calc_clearing_p * 1.2)
        max_y = max(max(supply_y) if supply_y else 0, max(demand_y) if demand_y else 0, calc_clearing_price * 1.2)
        plt.xlim(0, max_x * 1.1)
        plt.ylim(0, max_y * 1.1)
        plt.xlabel('电量 (MW)', fontsize=12)
        plt.ylabel('电价 (元/MWh)', fontsize=12)
        plt.title('电力现货市场供需阶梯曲线与出清点', fontsize=14, pad=20)
        plt.legend(fontsize=10, loc='upper right')
        plt.grid(True, alpha=0.3)

        # 保存并显示图片
        img_path = '/Users/rose/python_/py_gui/clearing_curve.png'
        plt.savefig(img_path, dpi=300, bbox_inches='tight')
        plt.close()
        os.system(f'open "{img_path}"')

        # 显示综合结果
        if result:
            self.showCombinedResult(result, img_path)

    def showCombinedResult(self, result, img_path):
        result_window = QDialog()
        result_window.setWindowTitle("出清综合结果")
        result_window.setGeometry(100, 100, 1200, 800)
        main_layout = QHBoxLayout()

        # 左侧：结果表格
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)
        price_label = QLabel(f"出清电价：{result.clearingPrice:.2f} 元/MWh")
        total_p_label = QLabel(f"总出清电量：{result.totalClearedP:.2f} MW")
        left_layout.addWidget(price_label)
        left_layout.addWidget(total_p_label)

        # 发电侧表格
        if result.plantResultCount > 0:
            plant_table = QTableWidget(result.plantResultCount, 3)
            plant_table.setHorizontalHeaderLabels(["电厂名称", "出清电量（MW）", "收入（元）"])
            for i in range(result.plantResultCount):
                plant_name = f"电厂{i + 1}"
                try:
                    if result.plantNames and result.plantNames[i]:
                        from ctypes import string_at
                        plant_name_bytes = string_at(result.plantNames[i])
                        try:
                            plant_name = plant_name_bytes.decode('utf-8')
                        except UnicodeDecodeError:
                            plant_name = plant_name_bytes.decode('gbk', errors='ignore')
                except:
                    plant_name = f"电厂{i + 1}"
                cleared_p = f"{result.plantClearedP[i]:.2f}" if result.plantClearedP and i < result.plantResultCount else "0.00"
                income = f"{result.plantIncome[i]:.2f}" if result.plantIncome and i < result.plantResultCount else "0.00"
                plant_table.setItem(i, 0, QTableWidgetItem(plant_name))
                plant_table.setItem(i, 1, QTableWidgetItem(cleared_p))
                plant_table.setItem(i, 2, QTableWidgetItem(income))
            left_layout.addWidget(plant_table)
        else:
            left_layout.addWidget(QLabel("无发电侧出清结果"))

        # 用户侧表格
        if result.userResultCount > 0:
            user_table = QTableWidget(result.userResultCount, 3)
            user_table.setHorizontalHeaderLabels(["用户名称", "出清负荷（MW）", "账单（元）"])
            for i in range(result.userResultCount):
                user_name = f"用户{i + 1}"
                try:
                    if result.userNames and result.userNames[i]:
                        from ctypes import string_at
                        user_name_bytes = string_at(result.userNames[i])
                        try:
                            user_name = user_name_bytes.decode('utf-8')
                        except UnicodeDecodeError:
                            user_name = user_name_bytes.decode('gbk', errors='ignore')
                except:
                    user_name = f"用户{i + 1}"
                cleared_p = f"{result.userClearedP[i]:.2f}" if result.userClearedP and i < result.userResultCount else "0.00"
                bill = f"{result.userBill[i]:.2f}" if result.userBill and i < result.userResultCount else "0.00"
                user_table.setItem(i, 0, QTableWidgetItem(user_name))
                user_table.setItem(i, 1, QTableWidgetItem(cleared_p))
                user_table.setItem(i, 2, QTableWidgetItem(bill))
            left_layout.addWidget(user_table)
        else:
            left_layout.addWidget(QLabel("无用户侧出清结果"))

        # 右侧：图表
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)
        if img_path and os.path.exists(img_path):
            try:
                pixmap = QPixmap(img_path)
                if not pixmap.isNull():
                    scaled_pixmap = pixmap.scaled(600, 400, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    image_label = QLabel()
                    image_label.setPixmap(scaled_pixmap)
                    right_layout.addWidget(image_label)
                else:
                    right_layout.addWidget(QLabel("图片加载失败"))
            except Exception as e:
                print(f"图片加载错误: {e}")
                right_layout.addWidget(QLabel("图片加载失败"))
        else:
            right_layout.addWidget(QLabel("图片文件不存在"))

        main_layout.addWidget(left_widget, 1)
        main_layout.addWidget(right_widget, 1)
        result_window.setLayout(main_layout)
        result_window.exec_()


# -------------------------- 7. 程序入口（未修改）--------------------------
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()


    # 程序退出时释放内存
    def on_app_quit():
        lib.initMarket()  # 复用initMarket释放申报数据内存
        print("程序退出，已释放所有申报数据内存")


    app.aboutToQuit.connect(on_app_quit)
    sys.exit(app.exec_())'''